Building a client-side asynchronous experience
Now that our synchronous service is up and running, let's see how we
interact with it from the client perspective. Go ahead and add a new Console Application project to our existing Visual Studio.NET solution. Right-click the new project and choose to Add a new Service Reference. After plugging in the URL of our IIS-hosted service, resist the temptation to immediately click the Ok button. Instead, let's visit the options we get after clicking the Advanced button.
Under the Client heading, notice a checkbox labeled Generate Asynchronous Operations. Once that is selected, we should exit out of this window and finish our service reference.
As a result of this wizard, we
end up with proxy class and application configuration files. Because I
plan on using this same client application over and over again
throughout this article, I've gone ahead and changed the Name attribute of the endpoint configuration in the application configuration file to something more descriptive such as AESyncEndpoint. Even though we checked that Generate Asynchronous Operations
box earlier, absolutely nothing prevents us from calling this service
in the traditional synchronous manner that it exposes. Such an example
looks like this:
class Program
{
static void Main(string[] args)
{
CallSyncServiceSync();
}
private static void CallSyncServiceSync()
{
Console.WriteLine("Calling sync service ...");
AdverseEventSyncClient client = new AdverseEventSyncClient("AESyncEndpoint");
try
{
AESyncServiceReference.AdverseEvent newAE = new AESyncServiceReference.AdverseEvent();
newAE.PatientID = 100912;
newAE.PhysicianID = 7543;
newAE.Product = "Cerinob";
newAE.ReportedBy = AESyncServiceReference.ReportedByType.Patient;
newAE.Category = AESyncServiceReference.AECategoryType. InjectionSoreness;
newAE.DateStarted = new DateTime(2008, 10, 29);
AESyncServiceReference.AdverseEventAction result = client.SubmitAdverseEvent(newAE);
Console.WriteLine("Service result returned ...");
Console.WriteLine("Should the patient reduce dosage? {0}", result.doReduceDosage.ToString());
client.Close();
Console.ReadLine();
}
catch (System.ServiceModel.CommunicationException) { client.Abort(); }
catch (System.TimeoutException) { client.Abort(); }
catch (System.Exception) { client.Abort(); throw; }
}
As we've discussed
earlier, while there is no problem with this pattern per se, we've
limited the ability to do anything else until that statement returns a
response. What if there is significant logic to determining the
appropriate action for an adverse event? What if a nurse actually needs
to review the event prior to disseminating a course of action?
There are a couple ways
to perform client-side asynchronous calls, but I'd like to highlight
the one just added for the .NET Framework 3.5. The classic mechanism
which uses the IAsyncResult object is still perfectly valid, and technically the only way you can go if you exploit the ChannelFactory directly. But, if you are using the proxy class, and want the cleanest route, then the new eventing capability in .NET 3.5 is a great choice.
If we dig into the service reference's Reference.cs file, we will find a generated statement that reveals a new client side event.
public event System.EventHandler<SubmitAdverseEventCompletedEventArgs> SubmitAdverseEventCompleted;
Our client code can now
register a handler for this event, and have it fire when the service
response is eventually returned. The client no longer needs to wait
until after executing the service request to continue processing other
items. What does this look like in our client code?
class Program
{
static void Main(string[] args)
{
//CallSyncServiceSync();
CallSyncServiceAsync();
}
private static void CallSyncServiceAsync()
{
Console.WriteLine("Calling sync service (async) ...");
AdverseEventSyncClient client = new AdverseEventSyncClient ("AESyncEndpoint");
try
{
AESyncServiceReference.AdverseEvent newAE = new AESyncServiceReference.AdverseEvent();
newAE.PatientID = 100912;
newAE.PhysicianID = 7543;
newAE.Product = "Cerinob";
newAE.ReportedBy = AESyncServiceReference.ReportedByType.Patient;
newAE.Category = AESyncServiceReference.AECategoryType. InjectionSoreness;
newAE.DateStarted = new DateTime(2008, 10, 29);
client.SubmitAdverseEventCompleted += new EventHandler<SubmitAdverse EventCompletedEventArgs>(client_SubmitAdverseEventCompleted);
client.SubmitAdverseEventAsync(newAE);
client.Close();
}
catch (System.ServiceModel.CommunicationException) { client.Abort(); }
catch (System.TimeoutException) { client.Abort(); }
catch (System.Exception) { client.Abort(); throw; }
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Doing other important things ...");
}
Console.ReadLine();
}
static void client_SubmitAdverseEventCompleted(object sender, SubmitAdverseEventCompletedEventArgs e)
{
Console.WriteLine(“Service result returned ...”);
Console.WriteLine(“Should the patient reduce dosage? {0}”, e.Result.doReduceDosage.ToString());
Console.ReadLine();
}
}
There are a number of interesting things to note here. See that I registered a completed
event handler, called the asynchronous version of the service
operation, and closed my proxy class. Also see that the event handler
has a strongly-typed argument that knows about the data members of my
service result. This is the result of the client execution:
Critical Point
Be completely aware
that this technique only simulates asynchronous behavior on a natively
synchronous service. You are still beholden to service timeouts and
other characteristics of a typical synchronous execution.
Working with server-side asynchronous services
As I just mentioned,
the above technique is simply a way to call synchronous services in a
non-blocking fashion. What if you want to design and expose a truly
asynchronous service? This is quite easy task in WCF. In our existing
WCF service library project, I added a brand new class to hold the
asynchronous service. This interface has the following definition:
[ServiceContract(Namespace = "http://Seroter.BizTalkSOA.Chapter6")]
public interface IAdverseEventAsync
{
[OperationContract(IsOneWay=true)]
void SubmitAdverseEvent(AdverseEvent NewAE);
}
The IsOneWay attribute is the key to forcing a truly asynchronous service.
Having a service operation simply return void
is not the same as making that operation asynchronous. While that may
give the impression of a exploiting a "fire-and-forget" pattern, in
fact the client executing the code is interacting with a
request/response operation and must still wait until the service
completes before progressing further.
Next, we need a new
service class which implements this interface. Once that is in place,
we must revisit our WCF service host project and add a new .svc
file whose directive points to our just-created service class. All
that's left is to update the web configuration file for the service
host container by adding an entry for the new service.
This client application call
is completely asynchronous. If I added a thirty second delay in the
service implementation or threw an exception in the service code, the
client would still move ahead immediately after the operation was
invoked. However, the operation DOES wait until a successful connection
to the server has been achieved and the appropriate HTTP 200 code is
returned. Once the client is assured that service infrastructure is up
and running, it will continue processing. Understand that this means
that the caller has no real assurance that the operation is completed
successfully, so this may not be appropriate for scenarios where
critical data is passed, or guarantees of once-only delivery is
required.